[DRAFT] Managed Harness Agents#9080
Conversation
jongio
left a comment
There was a problem hiding this comment.
A few things to sort out before this leaves draft.
Committed artifacts that look accidental:
docs/specs/managed-harness-agents/~$naged-agents-getting-started.docxis a Word lock/owner temp file. It shouldn't be tracked. Delete it and add~$*to.gitignore.cli/azd/extensions/azure.ai.agents/my-prompt-agent-0701-02/looks like generated scratch output: timestamped folder name, a full infra tree including a 1236-line generatedapplicationinsights-dashboard.bicep. Is this meant to ship, or is it leftover from a local run? If it's scratch, drop the whole directory.spec.docxandmanaged-agents-getting-started.docxare binaries committed next to their Python generators. Do you want the binaries tracked, or just the generators plus the.md?
Registry:
- The new
azure.ai.agentsentry points every artifact URL atgithub.com/kshitij-microsoft/azure-dev/releases/...(a personal fork). Every other entry in this file usesgithub.com/Azure/azure-dev/releases/.... This needs to point at official releases before merge, otherwise azd resolves this extension from a fork.
PR hygiene:
- The description is empty and the title is
[DRAFT]but the PR isn't marked as a draft. Add a description and flip it to draft if it isn't ready for review.
| // ResolvePromptTargetFromEnv applies azd environment-derived overrides to the | ||
| // prompt settings so both deploy and the lifecycle commands (show/invoke/list/ | ||
| // delete) target the same managed agent route. | ||
| // |
There was a problem hiding this comment.
This recover only logs and then re-panics, so the panic still tears down the extension process instead of returning an error to azd. Either return an error here (deploy handlers are expected to) or drop the recover, since as written it doesn't change the outcome.
| if _, err := client.GetByID(ctx, resourceID, storageAPIVersion, nil); err == nil { | ||
| return resourceID, nil // already exists | ||
| } |
| if _, err := client.GetByID(ctx, resourceID, keyVaultAPIVersion, nil); err == nil { | ||
| return resourceID, nil // already exists | ||
| } |
| func (provisionedDeploymentResolver) Create(_ context.Context, modelName string) error { | ||
| // Should not be reached given Exists always returns true; guard defensively | ||
| // with an actionable message rather than a silent no-op. | ||
| fmt.Fprintf(os.Stderr, | ||
| "Model deployment %q was not found. Provision it with `azd provision` "+ | ||
| "(deployments are declared in azure.yaml).\n", modelName) | ||
| return nil | ||
| } |
| return connActionFailFast, resolved, exterrors.Validation( | ||
| exterrors.CodeInvalidAgentManifest, | ||
| fmt.Sprintf( | ||
| "connection %q has no existing connection and no resolvable target", decl.Name, | ||
| ), | ||
| "set connections["+decl.Name+"].target, or set provision: true to create the backing resource", | ||
| ) |
| // Prompt (kind=managed) agents target the managed harness, not an ARM | ||
| // Foundry project. They self-authenticate via the harness client and carry | ||
| // their entire deploy target in the service config, so skip the | ||
| // subscription/tenant/credential resolution the hosted path needs. | ||
| if serviceIsPromptAgent(serviceConfig) { | ||
| fmt.Fprintf(os.Stderr, "Project path: %s, Service path: %s\n", proj.Project.Path, fullPath) | ||
| return p.resolveAgentDefinitionPath(proj.Project.Path, servicePath, fullPath) | ||
| } |
| @@ -1,5 +1,16 @@ | |||
| # Release History | |||
|
|
|||
| ## Unreleased | |||
| @@ -342,6 +396,31 @@ func postdownHandler(ctx context.Context, azdClient *azdext.AzdClient, args *azd | |||
| return nil | |||
| } | |||
|
|
|||
| // deletePromptAgentOnDown best-effort deletes a prompt agent from the harness | |||
| // during `azd down`. Failures are logged, never returned — teardown of the | |||
| // project should not be blocked by a harness hiccup. | |||
| func deletePromptAgentOnDown( | |||
| ctx context.Context, | |||
| svc *azdext.ServiceConfig, | |||
| settings *project.PromptAgentSettings, | |||
| ) { | |||
| settings.ApplyEnvOverrides() | |||
| if err := settings.Validate(); err != nil { | |||
| log.Printf("postdown: skipping harness delete for %q: %v", svc.Name, err) | |||
| return | |||
| } | |||
| client, err := project.NewPromptAgentClient(settings) | |||
| if err != nil { | |||
| log.Printf("postdown: failed to build harness client for %q: %v", svc.Name, err) | |||
| return | |||
| } | |||
| if _, err := client.DeleteAgent(ctx, svc.Name, settings.EffectiveAPIVersion(), true); err != nil { | |||
| log.Printf("postdown: failed to delete prompt agent %q from harness: %v", svc.Name, err) | |||
| return | |||
| } | |||
| fmt.Printf("Deleted prompt agent %q from the harness\n", svc.Name) | |||
| } | |||
jongio
left a comment
There was a problem hiding this comment.
Follow-up after the latest commit. The accidental scratch artifacts (the my-prompt-agent-0701-02/ tree, the ~$ Word lock file, and the committed .docx binaries) are gone now. Two items from my earlier review are still open.
The registry.json entry for azure.ai.agents still points every artifact URL at github.com/kshitij-microsoft/azure-dev/releases/... (9 URLs). Every other entry in that file uses github.com/Azure/azure-dev/releases/.... This has to point at official Azure/azure-dev releases before merge, otherwise azd resolves this extension from a personal fork.
The PR is still titled [DRAFT] with an empty description but isn't marked as a draft. Add a description and flip it to draft until it's ready for review.
jongio
left a comment
There was a problem hiding this comment.
Went deeper on the new Go code this pass. Two correctness issues.
One is inline on prompt_skills.go.
The other is in parseAgentEndpoint (cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint.go, around line 167). The new bypass path lets a dev pass http://localhost:5000 (scheme relaxed, port allowed), but the endpoint is then rebuilt as fmt.Sprintf("https://%s/api/projects/%s", host, projectSegment) where host is u.Hostname(). That forces the scheme back to https and drops the port, so an override like http://localhost:5000 resolves to https://localhost/... and requests never reach the local backend the override is meant to target. Preserve u.Scheme and u.Host (host+port) in the bypass case, the way validateProjectEndpoint does in project_endpoint.go.
| // The body starts after the closing `---` line. | ||
| after := rest[end+len("\n---"):] | ||
| after = strings.TrimPrefix(after, "-") // tolerate longer --- fences | ||
| after = strings.TrimLeft(after, "-\r\n") // consume the rest of the fence line |
There was a problem hiding this comment.
TrimLeft(after, "-\r\n") crosses the newline after the closing --- and keeps consuming -, so when a SKILL.md body's first line starts with a dash (a - bullet list, or a --- break) the leading dash is stripped from the instructions: - first bullet becomes first bullet. That mangled body flows into skillMeta.Instructions and ships to CreateSkillVersion. Trim only the remainder of the fence line (cut to the next \n) instead of a cut set that mixes - with newlines.
| if !strings.EqualFold(u.Scheme, "https") && | ||
| !(bypass && strings.EqualFold(u.Scheme, "http")) { |
| // DefaultPromptModelEndpoint is the model gateway the harness calls to reach | ||
| // the LLM. It is sent on invoke (Responses) requests via the x-model-endpoint | ||
| // header. | ||
| const DefaultPromptModelEndpoint = "https://va-dev-fdp-resource.services.ai.azure.com" |
| if _, err := client.GetByID(ctx, resourceID, storageAPIVersion, nil); err == nil { | ||
| return resourceID, nil // already exists | ||
| } |
| if _, err := client.GetByID(ctx, resourceID, keyVaultAPIVersion, nil); err == nil { | ||
| return resourceID, nil // already exists | ||
| } |
| reuse, _ := g.bindings[vectorStoreBindingKey].(string) | ||
| storeID, err := builder.EnsureVectorStore(ctx, g.managed.Name, reuse, files) |
| @@ -1,5 +1,17 @@ | |||
| # Release History | |||
|
|
|||
| ## Unreleased | |||
| // AgentKindPrompt is the Foundry "prompt" agent kind backed by the | ||
| // Prompt Execution Service (PES) Brain+Hand sandbox architecture. | ||
| // Lifecycle and response APIs live behind the same data-plane routes | ||
| // as the other Foundry kinds, with a "kind": "prompt" discriminator. | ||
| AgentKindPrompt AgentKind = "prompt" |
| PerCallPolicies: []policy.Policy{ | ||
| runtime.NewBearerTokenPolicy(cred, []string{"https://ai.azure.com/.default"}, nil), | ||
| azsdk.NewMsCorrelationPolicy(), |
| PerCallPolicies: []policy.Policy{ | ||
| runtime.NewBearerTokenPolicy(cred, []string{"https://ai.azure.com/.default"}, nil), | ||
| azsdk.NewMsCorrelationPolicy(), |
| return fmt.Errorf("marshaling request: %w", err) | ||
| } | ||
|
|
||
| req, err := runtime.NewRequest(ctx, http.MethodPost, targetURL) |
jongio
left a comment
There was a problem hiding this comment.
Re-reviewed at HEAD e991e5f. This commit (the ManagedAgent to PromptAgent rename plus the create-or-update version fallback) doesn't touch the items from my earlier reviews, and they're all still open:
- registry.json points every azure.ai.agents artifact URL at github.com/kshitij-microsoft/azure-dev/releases/... instead of Azure/azure-dev, so azd would resolve this extension from a personal fork.
- parseAgentEndpoint (cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint.go, around line 167) still rebuilds the endpoint as https://{u.Hostname()}/api/projects/..., which forces the scheme back to https and drops the port. An http://localhost:5000 override resolves to https://localhost/... and never reaches the local backend it's meant to target.
- My inline comments on service_target_prompt.go (the recover that logs and then re-panics) and prompt_skills.go (TrimLeft(after, "-\r\n") crossing the closing-fence newline and stripping a leading dash from the skill body) are unchanged.
Still titled [DRAFT] with an empty description and not marked as a draft.
| // DefaultPromptModelEndpoint is the model gateway the harness calls to reach | ||
| // the LLM. It is sent on invoke (Responses) requests via the x-model-endpoint | ||
| // header. | ||
| const DefaultPromptModelEndpoint = "https://va-dev-fdp-resource.services.ai.azure.com" |
| p, err := agent_api.BuildWorkspaceRoutePrefix( | ||
| settings.SubscriptionID, settings.ResourceGroup, settings.Workspace, | ||
| ) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("building workspace route prefix: %w", err) |
| if !strings.EqualFold(u.Scheme, "https") && | ||
| !(bypass && strings.EqualFold(u.Scheme, "http")) { |
| if !strings.EqualFold(u.Scheme, "https") && | ||
| !(bypass && strings.EqualFold(u.Scheme, "http")) { |
| info, statErr := os.Stat(full) | ||
| if statErr != nil { | ||
| return nil, fmt.Errorf("stat %q: %w", full, statErr) | ||
| } | ||
| if info.IsDir() { | ||
| continue | ||
| } |
| // Provision opts into the deploy engine creating the backing Azure resource | ||
| // (via an emitted Bicep module) when no existing connection or target can be | ||
| // resolved. Defaults to false (fail fast with guidance). |
| func TestTargetFromEnv(t *testing.T) { | ||
| env := map[string]string{ | ||
| "AI_PROJECT_DEPENDENT_RESOURCES": "foo=https://foo; conn=https://target", | ||
| } | ||
| if got := targetFromEnv("conn", env); got != "https://target" { |
| hostedSignalsPresent := userProvidedManifest || | ||
| flags.src != "" || | ||
| flags.deployMode != "" || | ||
| flags.runtime != "" || | ||
| flags.entryPoint != "" |
| @@ -1,5 +1,17 @@ | |||
| # Release History | |||
|
|
|||
| ## Unreleased | |||
| @@ -1 +1 @@ | |||
| 0.1.41-preview | |||
| 0.1.44-preview | |||
jongio
left a comment
There was a problem hiding this comment.
The two new commits bump the version to 0.1.44 and add a matching registry entry. That new entry re-adds the fork artifact URLs I flagged earlier (inline below). The other open items from my prior reviews aren't touched by these commits and are still open: the parseAgentEndpoint scheme/port drop in agent_endpoint.go, the recover-then-repanic in service_target_prompt.go, the TrimLeft in prompt_skills.go, and the [DRAFT] title plus empty description on a PR that isn't marked as a draft.
| "value": "8cae1b35438b2a79fed0b0b29fd423bffa0ced33b399dddb64042ea0b76b1ff2" | ||
| }, | ||
| "entryPoint": "azure-ai-agents-darwin-amd64", | ||
| "url": "https://github.com/kshitij-microsoft/azure-dev/releases/download/azd-ext-azure-ai-agents_0.1.44-preview/azure-ai-agents-darwin-amd64.zip" |
There was a problem hiding this comment.
This new 0.1.44 entry points all six artifact URLs at github.com/kshitij-microsoft/azure-dev/releases/... (a personal fork), the same as the earlier entries I flagged. Every other extension in this file resolves from github.com/Azure/azure-dev/releases/... These need to point at official Azure/azure-dev releases before merge, otherwise azd resolves this extension from a fork.
| if !strings.EqualFold(u.Scheme, "https") && | ||
| !(bypass && strings.EqualFold(u.Scheme, "http")) { |
| if strings.TrimSpace(reuseStoreID) != "" { | ||
| return reuseStoreID, nil | ||
| } | ||
|
|
||
| store, err := b.client.CreateVectorStore(ctx, name, fileIDs) |
| // Provision opts into the deploy engine creating the backing Azure resource | ||
| // (via an emitted Bicep module) when no existing connection or target can be | ||
| // resolved. Defaults to false (fail fast with guidance). | ||
| Provision bool `json:"provision,omitempty" yaml:"provision,omitempty"` |
| // Credentials carries auth material for non-Entra auth (e.g. an API key, | ||
| // possibly as a ${ENV_VAR} reference resolved at deploy time). | ||
| Credentials map[string]any `json:"credentials,omitempty" yaml:"credentials,omitempty"` |
| func (provisionedDeploymentResolver) Exists(context.Context, string) (bool, error) { | ||
| return true, nil |
| full := filepath.Join(dir, name) | ||
| info, statErr := os.Stat(full) | ||
| if statErr != nil { | ||
| return nil, fmt.Errorf("stat %q: %w", full, statErr) | ||
| } | ||
| if info.IsDir() { | ||
| continue | ||
| } |
| fmt.Fprint(w, payload.Delta) | ||
| wroteText = true | ||
| } | ||
| } else if strings.HasPrefix(event, "response.") { |
| if p.isPromptAgentService() { | ||
| settings, err := p.promptAgentSettings() | ||
| if err != nil { | ||
| return nil, err | ||
| } |
| // Prompt (kind=managed) agents are removed from the harness on down so | ||
| // `azd down` fully tears down the agent alongside the infrastructure. | ||
| // Best-effort: a harness failure is logged but does not block down. | ||
| if settings, isPrompt := promptSettingsFromService(svc); isPrompt { | ||
| deletePromptAgentOnDown(ctx, svc, settings) |
| if p.isPromptAgentService() { | ||
| settings, err := p.promptAgentSettings() | ||
| if err != nil { | ||
| return nil, err | ||
| } |
jongio
left a comment
There was a problem hiding this comment.
Re-reviewed at HEAD 9d92a95. I went through the four new commits (multi-turn memory via previous_response_id, the skill-bundle multipart upload, dropping the files/ scaffold, and the 0.1.45 version bump). No new correctness issues in the Go changes. The bundle change closes the gap where only SKILL.md was uploaded, and the added tests cover nested references/, assets/, and scripts/ files.
The new 0.1.45 registry entry re-adds the fork artifact URLs (inline below), so the registry problem I flagged earlier is back on this entry.
Open items from my prior reviews that these commits don't touch:
parseAgentEndpointin agent_endpoint.go still rebuilds the endpoint ashttps://{u.Hostname()}/api/projects/..., which forces the scheme back to https and drops the port, so anhttp://localhost:5000override resolves tohttps://localhost/...and never reaches the local backend.- The recover in service_target_prompt.go still logs and then re-panics instead of returning an error to azd.
- The
TrimLeft(after, "-\r\n")in prompt_skills.go extractFrontmatter is still there. Its impact is smaller now: after the bundle-upload refactor the parsed body is only used for the empty-instructions check, not uploaded, so a stripped leading dash no longer corrupts what the service stores. Still worth fixing so the validation sees the real body.
Still titled [DRAFT] with an empty description and not marked as a draft.
| "value": "38e1e008a153296a40c956c77546039e3a551e4e8c211e1f43ec72058ee6c516" | ||
| }, | ||
| "entryPoint": "azure-ai-agents-darwin-amd64", | ||
| "url": "https://github.com/kshitij-microsoft/azure-dev/releases/download/azd-ext-azure-ai-agents_0.1.45-preview/azure-ai-agents-darwin-amd64.zip" |
There was a problem hiding this comment.
This new 0.1.45 entry points all six artifact URLs at github.com/kshitij-microsoft/azure-dev/releases/... (a personal fork), same as the earlier entries. Every other extension in this file resolves from github.com/Azure/azure-dev/releases/... This has to point at official Azure/azure-dev releases before merge, otherwise azd resolves this extension from a fork.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 61 out of 61 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (13)
cli/azd/extensions/azure.ai.agents/internal/project/prompt_files.go:83
- azd-code-reviewer:
os.Statandos.ReadFileboth follow symlinks. A repository can therefore place a symlink underfiles/that points outside the project, causingazd deployto upload a local file such as credentials. Useos.Lstat, reject symlinks, and read only regular files.
cli/azd/extensions/azure.ai.agents/internal/project/prompt_files.go:213 - azd-code-reviewer: The hash map exists only on this newly constructed builder, and each graph starts with an empty
vector_store_idbinding. Consequently everyazd deployuploads every file and creates another vector store; the advertised cross-deploy hash dedupe/reuse never occurs. Persist or discover the prior store and uploaded file hashes before resolving this node.
cli/azd/extensions/azure.ai.agents/internal/project/prompt_deployment.go:78 - azd-code-reviewer: The production resolver unconditionally reports that every model deployment exists, so the node's create-if-missing branch is unreachable. A manually authored service or an agent whose model changed after provisioning proceeds without creating or even detecting the missing deployment, despite the changelog promising create-if-missing behavior. Query the actual deployment or return a validation error when it is absent.
cli/azd/extensions/azure.ai.agents/internal/cmd/list.go:92 - azd-code-reviewer: This performs only one unfiltered page request.
AgentListexposesHasMore/LastID, and the API accepts akindfilter, so a project with multiple kinds or more than one page will make this prompt-only command display non-prompt agents and omit later prompt agents. Requestkind=promptand follow pagination untilHasMoreis false.
list, err := client.ListAgents(ctx, nil, pctx.Settings.EffectiveAPIVersion())
cli/azd/extensions/azure.ai.agents/internal/cmd/listen.go:68
- azd-code-reviewer:
isHostedAgentServiceonly readsagent.yaml, while the service target explicitly supportsagent.ymlandAGENT_DEFINITION_PATH. For a hosted service using either supported alternative,hostedAgentCountremains zero and this handler writesENABLE_HOSTED_AGENTS=false, disabling required ACR/RBAC infrastructure. Count every non-prompt agent service as hosted, or reuse the service target's definition-path resolution.
if isHostedAgentService(svc, args.Project) {
hostedAgentCount++
}
cli/azd/extensions/azure.ai.agents/internal/cmd/invoke.go:370
- azd-code-reviewer: Prompt-service errors are discarded here. Invalid prompt settings or a malformed project resource ID therefore fall through to hosted protocol resolution and produce an unrelated error instead of the actionable prompt error. A non-prompt service already returns
(nil, false, nil), so returnpErrwhenever it is non-nil.
pctx, isPrompt, pErr := resolvePromptAgentService(ctx, azdClient, a.flags.name, a.noPrompt)
azdClient.Close()
if pErr == nil && isPrompt {
return a.runPromptInvoke(ctx, pctx)
}
// pErr (e.g. no azure.yaml) is non-fatal here: fall through to the
// existing hosted/local resolution which surfaces its own errors.
cli/azd/extensions/azure.ai.agents/internal/cmd/delete.go:109
- azd-code-reviewer: Prompt-service resolution errors are silently treated as “not prompt.” A malformed
promptAgenttarget then enters the hosted delete path and reports an unrelated missing endpoint/name error. ReturnpErrfirst; only fall back whenisPromptis false with no error.
if pctx, isPrompt, pErr := resolvePromptAgentService(
ctx, azdClient, a.flags.name, a.flags.noPrompt,
); pErr == nil && isPrompt {
return a.runPromptDelete(ctx, azdClient, pctx)
}
cli/azd/extensions/azure.ai.agents/internal/project/workspace_create.go:159
- azd-code-reviewer: Every storage existence error is treated as “not found.” A 403, cancellation, or transient ARM failure consequently triggers a PUT and obscures the original failure. Only create after an
azcore.ResponseErrorwith status 404; return all other errors.
cli/azd/extensions/azure.ai.agents/internal/project/workspace_create.go:197 - azd-code-reviewer: Every key-vault lookup error is treated as “not found.” On authorization, cancellation, or transient failures this attempts an unnecessary create/update and loses the useful original context. Proceed only on a 404 response and return other errors.
cli/azd/extensions/azure.ai.agents/CHANGELOG.md:3 - azd-code-reviewer: Release tooling does not recognize a bare
## Unreleased; its parser requires a versioned## <semver> (Unreleased)heading. Because this PR bumps the extension to 0.1.46-preview, this section will be skipped by changelog generation unless the heading includes that version.
## Unreleased
cli/azd/extensions/azure.ai.agents/internal/cmd/listen.go:418
- azd-code-reviewer: Post-down cleanup uses the raw service name and un-overlaid settings, while deployment uses the name from the agent definition and resolves
ProjectEndpoint/workspace from azd environment values. Services whose agent name differs, or no-prompt projects whose endpoint came from provisioning outputs, will delete the wrong route and leave the managed agent behind. Resolve the same deployed identity and target used by deploy before callingDeleteAgent.
if _, err := client.DeleteAgent(ctx, svc.Name, settings.EffectiveAPIVersion(), true); err != nil {
log.Printf("postdown: failed to delete prompt agent %q from harness: %v", svc.Name, err)
cli/azd/extensions/azure.ai.agents/internal/cmd/prompt_service.go:101
- azd-code-reviewer: Lifecycle commands hardcode
agent.yaml, although deployment supportsagent.ymlandAGENT_DEFINITION_PATH. For those valid layouts this silently falls back to the service name and leaves the model empty; show/delete can target the wrong agent and invoke sends an empty model. Resolve the definition with the same helper/rules used by the service target and surface read/parse errors instead of silently changing identity.
if data, readErr := os.ReadFile(filepath.Join(pctx.ServiceDir, "agent.yaml")); readErr == nil {
cli/azd/extensions/azure.ai.agents/internal/cmd/invoke_managed.go:169
- azd-code-reviewer:
response.failedanderrorSSE events are consumed as successful lifecycle events. The command can print partial output, exit with status 0, and persist a response ID even though the harness reported failure. Parse these event payloads and return their error after the stream (or immediately) instead of treating every non-delta event as success.
} else if strings.HasPrefix(event, "response.") {
// Capture the response id from any lifecycle event that carries
// it (e.g. response.created, response.completed). The last one
// seen wins so the persisted id reflects the completed turn.
var payload struct {
| return agent_api.NewManagedAgentClient(agent_api.ManagedAgentClientOptions{ | ||
| BaseURL: baseURL, | ||
| RoutePrefix: prefix, | ||
| Credential: promptCredential(), | ||
| Scopes: promptScopesForBaseURL(baseURL), |
jongio
left a comment
There was a problem hiding this comment.
Re-reviewed at HEAD b97ede5. The one new commit since my last pass (the 1.46-preview release) adds the toolbox project-connection wiring (ensureToolboxConnection / FoundryConnectionsARMClient, and injectMcpTool now carrying project_connection_id), the show.go harness/endpoint/toolbox output, and seeding AZURE_LOCATION from the project region. I went through the new Go in prompt_skills.go, foundry_connections_controlplane.go, show.go, and init_managed_foundry.go and didn't find new correctness issues there.
The open items from my earlier reviews are all still open:
-
registry.json: the new 0.1.46 entry re-adds the fork artifact URLs, so everyazure.ai.agentsURL from 0.1.42 through 0.1.46 (30 of them) points atgithub.com/kshitij-microsoft/azure-dev/releases/.... Every other entry in the file usesgithub.com/Azure/azure-dev/releases/.... These have to point at official Azure/azure-dev releases before merge, otherwise azd resolves this extension from a personal fork. -
parseAgentEndpoint(agent_endpoint.go) still rebuilds the endpoint ashttps://{u.Hostname()}/api/projects/.... The new comment on the port check even says the override path allowshttp://localhost:5000for local backends, but the rebuild drops the port and forces https, sohttp://localhost:5000still resolves tohttps://localhost/...and never reaches the local backend it targets. Preserveu.Schemeandu.Host(host+port) in the bypass case, the wayvalidateProjectEndpointdoes. -
service_target_prompt.godeployPromptAgentstill recovers, logs the stack, then re-panics instead of returning an error to azd. -
prompt_skills.goextractFrontmatterstill doesTrimLeft(after, "-\r\n"), which crosses the newline after the closing fence and strips a leading dash from the body when it starts with a list item. Smaller impact now that the parsed body only feeds the empty-instructions check, but still worth fixing so validation sees the real body.
Still titled [DRAFT] with an empty description and not marked as a draft. Add a description and flip it to draft until it's ready for review.
No description provided.